#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/wait.h>



#define IMX6UIRQ_CNT    1        /* 设备号个数 */
#define IMX6UIRQ_NAME  "noblockio" /* 名字 */   
#define KEY0VALUE     0X01       /* key0的按键值 */
#define INVAKEY       0XFF       /* 无效的按键值 */
#define KEY_NUM       1          /* 按键数量 */


/* 中断io描述结构体 */
struct irq_keydesc{
    int gpio;
    int irqnum;                /* 中断号 */
    unsigned char value;     /* 按键对应的键值 */
    char name[10];             /* 名字 */
    irqreturn_t (*handler)(int, void *);  /*  中断服务函数*/
};

/* imx6uirq设备结构体 */
struct imx6uirq_dev
{
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd;
    atomic_t keyvalue;                 /* 有效的按键值 */
    atomic_t releasekey;                /* 标记是否完成一次完整的按键 */
    struct timer_list timer;            /* 定义一个定时器 */
    struct irq_keydesc irqkeydesc;  /* 按键描述数组 */
    unsigned char curkeynum;         /* 当前的按键号 */
    wait_queue_head_t r_wait;       /* 等待队列头 */
};

struct imx6uirq_dev imx6uirq;   /* irq设备 */


static irqreturn_t key0_handler(int irq,void *dev_id)
{
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
    

    dev->curkeynum = 0;

    dev->timer.data = (volatile long)dev_id;

    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));

    return IRQ_RETVAL(IRQ_HANDLED);
}

void timer_function(unsigned long arg)
{
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
    unsigned char keyvalue;

    keyvalue = gpio_get_value(dev->irqkeydesc.gpio);
    if (keyvalue == 0)        /*  按键被按下 */
    {
        atomic_set(&dev->keyvalue, dev->irqkeydesc.value);
    }
    else{
        atomic_set(&dev->keyvalue, 0x80 | dev->irqkeydesc.value);
        atomic_set(&dev->releasekey, 1);/* 松开标记 */
        
    }

    if (atomic_read(&dev->releasekey)){
        wake_up_interruptible(&dev->r_wait);
    }
    
}

static int keyio_init(void)
{
    int ret = 0;
    imx6uirq.nd = of_find_node_by_path("/key");
    if(!imx6uirq.nd){
        printk("key node not find!\r\n");
        return -EINVAL;
    }

    /* 提取GPIO */
    imx6uirq.irqkeydesc.gpio = of_get_named_gpio(imx6uirq.nd, "key-gpio", 0);
    if(!gpio_is_valid(imx6uirq.irqkeydesc.gpio)){
        printk("can't get key0 !\r\n");
        return -EINVAL;
    }

    memset(imx6uirq.irqkeydesc.name, 0, sizeof(imx6uirq.irqkeydesc.name));
    sprintf(imx6uirq.irqkeydesc.name, "key0");
    gpio_request(imx6uirq.irqkeydesc.gpio,imx6uirq.irqkeydesc.name);
    gpio_direction_input(imx6uirq.irqkeydesc.gpio);

    /*  获取中断号 */
    imx6uirq.irqkeydesc.irqnum = irq_of_parse_and_map(imx6uirq.nd, 0);
    printk("key0:gpio = %d,irqnum = %d \r\n", imx6uirq.irqkeydesc.gpio, imx6uirq.irqkeydesc.irqnum);

    /* 申请中断 */
    imx6uirq.irqkeydesc.handler = key0_handler;
    imx6uirq.irqkeydesc.value = KEY0VALUE;

    /* 请求irq */
    ret = request_irq(imx6uirq.irqkeydesc.irqnum,imx6uirq.irqkeydesc.handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                        imx6uirq.irqkeydesc.name,&imx6uirq);
    if(ret < 0){
        printk("irq %d request failed! \r\n", imx6uirq.irqkeydesc.irqnum);
        return -EFAULT;
    }

    /*  初始化定时器 */
    init_timer(&imx6uirq.timer);
    imx6uirq.timer.function = timer_function;

    return 0;
}

static int imx6uirq_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &imx6uirq;

    return 0;
}

static int imx6uirq_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt)
{
    int ret = 0;
    unsigned int keyvalue = 0;
    unsigned int releasekey = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;



    if (filp->f_flags & O_NONBLOCK){
        if(atomic_read(&dev->releasekey) == 0)
            return -EAGAIN;
    }
    else
    {
       ret =  wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey));
       if (ret)
       {
           return ret;
       }
    }

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);
    if (releasekey)   /*  如果按键已经松开 */
    {   
        if (keyvalue & 0x80)  /* 判断按键是否松开，其实到这里，实际已经松开了，但是为了安全，又判断了一次 */
        { 
            /* 按键已经松开 */
            keyvalue &= ~0x80;  /* 清除按下标志   最高位清零*/
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));/* 发送按键值 0x01 */
        }else {
            return -EINVAL;
        }
        atomic_set(&dev->releasekey, 0);   /* 清零标志，表示被按下的按键已经被读取 */
        return 0;
    }else{
        return -EINVAL;
    }


}

unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait)
{
    unsigned int mask = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
    poll_wait(filp, &dev->r_wait, wait);
    if(atomic_read(&dev->releasekey)){
        mask = POLLIN | POLLRDNORM;
    }
    return mask;
}

static struct file_operations imx6uirq_fops = {
    .owner	= THIS_MODULE,
    .open	= imx6uirq_open,
    .read	= imx6uirq_read,
    .poll   = imx6uirq_poll
};


static __init imx6uirq_init(void)
{
    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if(imx6uirq.major){   /* 如果定义了设备号 */
        imx6uirq.devid = MKDEV(imx6uirq.major,0);
        register_chrdev_region(imx6uirq.devid,IMX6UIRQ_CNT,IMX6UIRQ_NAME);
    }else {    /* 没有定义设备号 */
        alloc_chrdev_region(&imx6uirq.devid,0,IMX6UIRQ_CNT,IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);  /* 获取分配的主设备号 */
        imx6uirq.minor = MINOR(imx6uirq.devid);  /* 获取分配的次设备号 */
    }

    /* 2、初始化cdev */
    imx6uirq.cdev.owner = THIS_MODULE;
    cdev_init(&imx6uirq.cdev,&imx6uirq_fops);

    /* 3、添加一个cdev */
    cdev_add(&imx6uirq.cdev,imx6uirq.devid,IMX6UIRQ_CNT);
    /* 4、创建类 */
    imx6uirq.class = class_create(THIS_MODULE,IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.class)){
        return PTR_ERR(imx6uirq.class);
    }

    /* 5、创建设备 */
    imx6uirq.device = device_create(imx6uirq.class,NULL,imx6uirq.devid,NULL,IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.device)){
        return PTR_ERR(imx6uirq.device);
    }

    atomic_set(&imx6uirq.keyvalue,INVAKEY);
    atomic_set(&imx6uirq.releasekey,0);
    keyio_init();

    init_waitqueue_head(&imx6uirq.r_wait);

    return 0;
}

static __exit imx6uirq_exit(void)
{
    del_timer_sync(&imx6uirq.timer);
    free_irq(imx6uirq.irqkeydesc.irqnum, &imx6uirq);
    gpio_free(imx6uirq.irqkeydesc.gpio );

    // 注销字符设备驱动
    cdev_del(&imx6uirq.cdev);
    unregister_chrdev_region(imx6uirq.devid,IMX6UIRQ_CNT);
    
    device_destroy(imx6uirq.class,imx6uirq.devid);
    class_destroy(imx6uirq.class);

}

module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhangchaoliang");
